/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012 Oliver Katz */

#include "backend_directfb.h"

namespace sleepyface
{
	namespace visual
	{
		DFBEnumerationResult enumerateInputDevice(unsigned int id, DFBInputDeviceDescription dsc, void *data)
		{
			map<unsigned int, DFBInputDeviceDescription> *devs = (map<unsigned int, DFBInputDeviceDescription> *)data;
			(*devs)[id] = dsc;
			return DFENUM_OK;
		}

		Backend::Backend()
		{
		}

		int Backend::init(int *argc, char ***argv)
		{
			DFBSurfaceDescription dsc;
			if (DirectFBInit(argc, argv) != DFB_OK)
				return 1;
			if (DirectFBCreate(&dfb) != DFB_OK)
				return 2;
			if (dfb->SetCooperativeLevel(dfb, DFSCL_FULLSCREEN) != DFB_OK)
				return 3;
			dsc.flags = (DFBSurfaceDescriptionFlags)(DSDESC_CAPS);
			dsc.caps = (DFBSurfaceCapabilities)(DSCAPS_PRIMARY | DSCAPS_FLIPPING);
			if (dfb->CreateSurface(dfb, &dsc, &pri) != DFB_OK)
				return 4;
			if (pri->GetSize(pri, &width, &height) != DFB_OK)
				return 5;
			if (dfb->EnumInputDevices(dfb, enumerateInputDevice, &devs) != DFB_OK)
				return 6;
			if (dfb->CreateInputEventBuffer(dfb, DICAPS_ALL, DFB_FALSE, &ebuf) != DFB_OK)
				return 7;
			return 0;
		}

		int Backend::halt()
		{
			ebuf->Release(ebuf);
			pri->Release(pri);
			dfb->Release(dfb);
			return 0;
		}

		string Backend::setName(string n)
		{
			return (name = n);
		}

		string Backend::getName()
		{
			return name;
		}

		int Backend::getWidth()
		{
			return width;
		}

		int Backend::setWidth(int w)
		{
			return -1;
		}

		int Backend::getHeight()
		{
			return height;
		}

		int Backend::setHeight(int h)
		{
			return -1;
		}

		int Backend::rect(int x0, int y0, int x1, int y1, int r, int g, int b, int a)
		{
			if (pri->SetColor(pri, r, g, b, a) != DFB_OK)
				return 1;
			if (pri->FillRectangle(pri, x0, y0, x1-x0, y1-y0) != DFB_OK)
				return 2;
			return 0;
		}

		int Backend::line(int x0, int y0, int x1, int y1, int r, int g, int b, int a)
		{
			if (pri->SetColor(pri, r, g, b, a) != DFB_OK)
				return 1;
			if (pri->DrawLine(pri, x0, y0, x1, y1) != DFB_OK)
				return 2;
			return 0;
		}

		int Backend::imageGetSize(string path, int &w, int &h)
		{
			IDirectFBImageProvider *pro;
			if (dfb->CreateImageProvider(dfb, path.c_str(), &pro) != DFB_OK)
				return 1;
			DFBSurfaceDescription dsc;
			if (pro->GetSurfaceDescription(pro, &dsc) != DFB_OK)
				return 2;
			w = dsc.width;
			h = dsc.height;
			pro->Release(pro);
			return 0;
		}

		int Backend::image(int x, int y, string path)
		{
			IDirectFBImageProvider *pro;
			IDirectFBSurface *surf;
			if (dfb->CreateImageProvider(dfb, path.c_str(), &pro) != DFB_OK)
				return 1;
			DFBSurfaceDescription dsc;
			if (pro->GetSurfaceDescription(pro, &dsc) != DFB_OK)
				return 2;
			if (dfb->CreateSurface(dfb, &dsc, &surf) != DFB_OK)
				return 3;
			if (pro->RenderTo(pro, surf, NULL) != DFB_OK)
				return 4;
			if (pri->Blit(pri, surf, NULL, x, y) != DFB_OK)
				return 5;
			surf->Release(surf);
			pro->Release(pro);
			return 0;
		}

		int Backend::loadFont(string name, string path, int size)
		{
			DFBFontDescription fdsc;
			fdsc.flags = DFDESC_HEIGHT;
			fdsc.height = size;
			IDirectFBFont *font;
			if (dfb->CreateFont(dfb, path.c_str(), &fdsc, &font) != DFB_OK)
				return 1;
			fonts[name] = font;
			return 0;
		}

		int Backend::unloadFont(string name)
		{
			fonts[name]->Release(fonts[name]);
		}

		int Backend::getTextWidth(string font, string text)
		{
			int w = -1;
			if (fonts[font]->GetStringWidth(fonts[font], text.c_str(), -1, &w) != DFB_OK)
				return -1;
			return w;
		}

		int Backend::text(int x0, int y0, int r, int g, int b, int a, string font, string s)
		{
			if (pri->SetFont(pri, fonts[font]) != DFB_OK)
				return 1;
			if (pri->SetColor(pri, r, g, b, a) != DFB_OK)
				return 2;
			if (pri->DrawString(pri, s.c_str(), -1, x0, y0, DSTF_LEFT) != DFB_OK)
				return 3;
			return 0;
		}

		int Backend::update()
		{
			if (pri->Flip(pri, NULL, (DFBSurfaceFlipFlags)0) != DFB_OK)
				return 1;
			return 0;
		}

		int Backend::bindDisplay(void (*cb)(void))
		{
			displayCallback = cb;
			return 0;
		}

		int Backend::bindKeyPress(void (*cb)(unsigned char k, int x, int y))
		{
			keyPressCallback = cb;
			return 0;
		}

		int Backend::bindKeyRelease(void (*cb)(unsigned char k, int x, int y))
		{
			keyReleaseCallback = cb;
			return 0;
		}

		int Backend::bindHover(void (*cb)(int x, int y))
		{
			hoverCallback = cb;
			return 0;
		}

		int Backend::bindDrag(void (*cb)(int x, int y))
		{
			dragCallback = cb;
			return 0;
		}

		int Backend::bindButton(void (*cb)(int b, int s, int x, int y))
		{
			buttonCallback = cb;
			return 0;
		}

		int Backend::async()
		{
			int cx = width*0.5;
			int cy = height*0.5;
			bool b1down = false;

			run = true;
			while (run)
			{
				DFBInputEvent e;
				if (ebuf->WaitForEvent(ebuf) != DFB_OK)
					return 1;

				while (ebuf->GetEvent(ebuf, DFB_EVENT(&e)) == DFB_OK)
				{
					if (e.type == DIET_KEYRELEASE)
					{
						keyPressCallback(e.key_symbol, cx, cy);
					}
					else if (e.type == DIET_KEYPRESS)
					{
						keyReleaseCallback(e.key_symbol, cx, cy);
					}
					else if (e.type == DIET_AXISMOTION)
					{
						if (e.flags & DIEF_AXISABS)
						{
							if (e.axis == DIAI_X)
							{
								cx += (e.axisabs-e.min)/e.max;
							}
							else if (e.axis == DIAI_Y)
							{
								cy += (e.axisabs-e.min)/e.max;
							}
						}
						else if (e.flags & DIEF_AXISREL)
						{
							if (e.axis == DIAI_X)
							{
								cx += e.axisrel;
							}
							else if (e.axis == DIAI_Y)
							{
								cy += e.axisrel;
							}
						}
						if (b1down)
							dragCallback(cx, cy);
						else
							hoverCallback(cx, cy);
					}
					else if (e.type == DIET_BUTTONPRESS)
					{
						buttonCallback(e.button, 1, cx, cy);
						if (e.button == 1)
							b1down = true;
					}
					else if (e.type == DIET_BUTTONRELEASE)
					{
						buttonCallback(e.button, 0, cx, cy);
						if (e.button == 1)
							b1down = false;
					}
				}
			}

			return 0;
		}

		IDirectFB *Backend::getDFB()
		{
			return dfb;
		}

		IDirectFBSurface *Backend::getDFBSurface()
		{
			return pri;
		}

		extern "C"
		{
			Backend *_vb;
		}
	}
}
